<?php

declare(strict_types=1);

/*
 * This file is part of eelly package.
 *
 * (c) eelly.com
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eelly\SDK;

/*
 * This file is part of eelly package.
 *
 * (c) eelly.com
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use PhpParser\BuilderFactory;
use PhpParser\Comment\Doc;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Declare_;
use PhpParser\Node\Stmt\DeclareDeclare;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\TraitUse;
use PhpParser\Node\Stmt\Use_;
use PhpParser\ParserFactory;
use Symfony\Component\Finder\Finder;

error_reporting(-1);
ini_set('display_errors', '1');

\defined('ROOT_PATH') || \define('ROOT_PATH', realpath(\dirname(__DIR__.'/../../../../../..')));
chdir(ROOT_PATH);
require 'vendor/autoload.php';

$finder = new Finder();
$eellyRootPath = ROOT_PATH;

function compile(string $file): void
{
    $lexer = new \PhpParser\Lexer\Emulative([
        'usedAttributes' => [
            'comments',
            'startLine', 'endLine',
            'startTokenPos', 'endTokenPos',
            'shortArraySyntax',
        ],
    ]);
    $parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7, $lexer);
    $stmts = $parser->parse(file_get_contents($file));
    $factory = new BuilderFactory();
    $nodes = [
        new Declare_([new DeclareDeclare('strict_types', new LNumber(1))]),
    ];
    foreach ($stmts as $item) {
        // namespace
        if ($item instanceof Namespace_) {
            $moduleName = $item->name->parts[0];
            $namespace = __NAMESPACE__.'\\'.$moduleName.'\\Api';
            $node = $factory->namespace($namespace);
            $clientNameAlias = 'Client'.date('Ymd');
            $node->addStmt($factory->use(__NAMESPACE__.'\\EellyClient as '.$clientNameAlias));
            $node->addStmt($factory->use(__NAMESPACE__.'\\GetInstanceTrait'));
            foreach ($item->getAttribute('comments') as $comment) {
                $node->setDocComment(new Doc("\n".$comment->getText()));
            }
            foreach ($item->stmts as $classStmt) {
                if ($classStmt instanceof Class_) {
                    // class
                    $classShortName = substr((string) $classStmt->name, 0, -5);
                    $className = $classShortName;
                    if (class_exists($namespace.'\\'.$className)) {
                        return;
                    }
                    $newClass = $factory->class($className);
                    $newClass->setDocComment(new Doc(sprintf("\n/**
 * This class has been auto-generated by shadon compiler (%s).
 */", date('Y-m-d H:i:s'))));
                    // methods

                    foreach ($classStmt->stmts as $methodStmt) {
                        if ($methodStmt instanceof ClassMethod && $methodStmt->isPublic()) {
                            $methodName = (string) $methodStmt->name;
                            if ('onConstruct' == $methodName) {
                                continue;
                            }
                            $returnType = $methodStmt->getReturnType();
                            if (null === $returnType) {
                                $returnType = new Identifier('void');
                            }
                            apiMethod($factory, $methodName, $returnType, $methodStmt, $moduleName, $classShortName, $clientNameAlias, $newClass);
                            apiMethod($factory, $methodName, $returnType, $methodStmt, $moduleName, $classShortName, $clientNameAlias, $newClass, false);
                        }
                    }
                    $newClass->addStmt(new TraitUse([new Name('GetInstanceTrait')]));
                    $node->addStmt($newClass);
                } elseif ($classStmt instanceof Use_) {
                    // use
                    $useClass = '\\'.implode('\\', $classStmt->uses[0]->name->parts);
                    if ('\\GuzzleHttp\\Psr7\\UploadedFile' == $useClass || (class_exists($useClass) && 'DTO' == substr($useClass, -3))) {
                        $node->addStmt($classStmt);
                    }
                }
            }
            $nodes[] = $node->getNode();
        }
    }
    // ready
    $prettyPrinter = new \PhpParser\PrettyPrinter\Standard(['shortArraySyntax' => true]);
    $newCode = $prettyPrinter->prettyPrintFile($nodes);
    $sdkPath = 'vendor/eelly/eelly-sdk-php/src/SDK';
    $sdkFile = $sdkPath.'/'.$moduleName.'/Api/'.$className.'.php';
    file_put_contents($sdkFile, $newCode);
    echo 'generate new sdk file '.$sdkFile."\n";
}

/**
 * @param BuilderFactory $factory
 * @param string         $methodName
 * @param $returnType
 * @param $methodStmt
 * @param string $moduleName
 * @param $classShortName
 * @param string                    $clientNameAlias
 * @param \PhpParser\Builder\Class_ $newClass
 */
function apiMethod(BuilderFactory $factory, string $methodName, $returnType, $methodStmt, string $moduleName, $classShortName, string $clientNameAlias, \PhpParser\Builder\Class_ $newClass, bool $sync = true): void
{
    $newMethod = $factory
        ->method($methodName.($sync?'':'Async'))
        ->makePublic()
        ->makeStatic();
    if ($sync && $returnType->getAttributes()) {
        $newMethod
            ->setReturnType($returnType);
    }
    $docs = $methodStmt->getAttribute('comments');
    if (null === $docs) {
        $strr = str_repeat('=', 100);
        $newMethod->setDocComment(new Doc(sprintf("\n/**
 * %s
 * %sWARNING: lost comments
 * %s
 */", $strr, str_repeat(' ', 40), $strr)));
    } else {
        foreach ($docs as $doc) {
            if ($doc instanceof Doc) {
                $newMethod->setDocComment($doc);
            }
        }
    }
    // args
    $args = [
        new \PhpParser\Node\Arg(new \PhpParser\Node\Scalar\String_(
                sprintf('%s/%s', lcfirst($moduleName), lcfirst($classShortName)))
        ),
        new \PhpParser\Node\Arg(new \PhpParser\Node\Scalar\String_($methodName)),
    ];
    // params
    $argsBag = [];
    foreach ($methodStmt->getParams() as $param) {
        $newMethod->addParam($param);
        if ($param->type instanceof Name && 'UidDTO' == $param->type->parts[0]) {
            continue;
        }
        $argsBag[] = new \PhpParser\Node\Expr\ArrayItem(
            new \PhpParser\Node\Expr\Variable($param->var->name),
            new \PhpParser\Node\Scalar\String_($param->var->name)
        );
    }
    $args[] = new \PhpParser\Node\Arg(new \PhpParser\Node\Expr\Array_($argsBag));
    $args[] = new \PhpParser\Node\Arg(new ConstFetch(new Name($sync ? 'true' : 'false')));
    if ($sync && $returnType instanceof Identifier && 'void' == $returnType->name && $returnType->getAttributes()) {
        $newMethod->addStmt(new StaticCall(new Name($clientNameAlias), new Identifier('requestJson'), $args));
    } else {
        $newMethod->addStmt(new Return_(
            new StaticCall(new Name($clientNameAlias), new Identifier('requestJson'), $args)
        ));
    }
    $newClass->addStmt($newMethod);
}

foreach ($finder->in($eellyRootPath.'/src/*/Logic')->depth('< 1')->name('*Logic.php') as $file) {
    /* @var \Symfony\Component\Finder\SplFileInfo $file */
    compile($file->getRealPath());
}
